---
title: StateProvider
---

import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";
import CodeBlock from "@theme/CodeBlock";
import product from "!!raw-loader!/docs/providers/state_provider/product.dart";
import productListView from "!!raw-loader!/docs/providers/state_provider/product_list_view.dart";
import dropdown from "!!raw-loader!/docs/providers/state_provider/dropdown.dart";
import sortProvider from "!!raw-loader!/docs/providers/state_provider/sort_provider.dart";
import connectedDropdown from "!!raw-loader!/docs/providers/state_provider/connected_dropdown.dart";
import sortedProductProvider from "!!raw-loader!/docs/providers/state_provider/sorted_product_provider.dart";
import updateReadTwice from "!!raw-loader!/docs/providers/state_provider/update_read_twice.dart";
import updateReadOnce from "!!raw-loader!/docs/providers/state_provider/update_read_once.dart";
import { trimSnippet } from "../../../../../src/components/CodeSnippet";

StateProvider是一个公开了一种修改其状态的方法的provider。
它是 [StateNotifierProvider] 的简化版，旨在避免为非常简单的用例编写 [StateNotifier] 类。

`StateProvider` 的存在主要是为了允许用户界面对**简单**的变量进行修改。
所以`StateProvider` 的状态通常为：

- 枚举类型，例如筛选器类型
- 一段字符串(String)，通常是输入框的原始内容
- 用于复选框的布尔类型
- 用于分页或年龄表单字段的数字

你不应该使用 `StateProvider` 如果：

- 你的状态需要验证逻辑
- 你的状态是一个复杂的对象 (比如自定义的类, 集合……)
- 修改状态的逻辑比简单的 `count++` 更复杂

对于更复杂的情况，可以考虑使用 [StateNotifierProvider] ，并创建一个 [StateNotifier] 类。
虽然最初的样板文件会有点大，
但有一个自定义的 [StateNotifier] 类对于项目的长期可维护性是至关重要的，
因为它将状态的业务逻辑集中在了一个地方。

## 使用示例：使用下拉菜单更改筛选类型

`StateProvider` 的一个真实的用例是管理简单表单组件的状态，比如下拉菜单/输入框/复选框。
特别来说，我们将看到如何使用 `StateProvider` 实现一个下拉菜单，
该下拉菜单允许更改产品列表的排序方式。

为了简单起见，我们将在应用中直接构建将获得的产品列表，如下所示：

<CodeBlock>{trimSnippet(product)}</CodeBlock>

在真实的应用程序中，我们通常会使用 [FutureProvider] 通过网络请求来获得该列表。

然后，可以在用户界面上通过下面的操作来显示产品列表：

<CodeBlock>{trimSnippet(productListView)}</CodeBlock>

现在我们已经完成了基础，我们可以添加一个下拉菜单，
它将按价格或名称过滤我们的产品。
为此，我们将使用[DropDownButton](https://api.flutter.dev/flutter/material/DropdownButton-class.html)。

<CodeBlock>{trimSnippet(dropdown)}</CodeBlock>

现在我们有了一个下拉列表，
让我们创建一个 `StateProvider` 并将下拉菜单的状态与我们的provider同步。

首先，让我们创建一个 `StateProvider`：

<CodeBlock>{trimSnippet(sortProvider)}</CodeBlock>

然后，我们可以通过下面的操作将这个provider与我们的下拉菜单连接起来：

<CodeBlock>{trimSnippet(connectedDropdown)}</CodeBlock>

有了这些，我们现在应该能够更改筛选的类型。
不过它对产品列表没有影响！
现在是最后一部分：更新我们的 `productsProvider` 以对产品列表进行排序。

实现这一点的关键是使用 [ref.watch]，
让我们的 `productsProvider` 获得排序类型，
并在排序类型更改时重新计算产品列表。

代码实现会是：

<CodeBlock>{trimSnippet(sortedProductProvider)}</CodeBlock>

就是这样！这个变改足以让用户界面在排序类型更改时自动重绘产品列表。

下面是在Dartpad上完整的例子：

<iframe
  src="https://dartpad.dev/embed-flutter.html?gh_owner=rrousselGit&gh_repo=riverpod&gh_path=website%2Fdocs%2Fproviders%2Fstate_provider"
  style={{ border: 0, width: "100%", aspectRatio: "2/1.5" }}
></iframe>

## 如何在不读取provider两次的情况下根据之前的值更新状态

有时你希望根据之前的值更新 `StateProvider` 的状态。
自然而然，你可能会这样写：

<CodeBlock>{trimSnippet(updateReadTwice)}</CodeBlock>

虽然这段代码没有什么特别的错误，但是语法上着实有点不太方便。

为了更方便地使用，我们可以使用 `update` 函数。
这个函数将接受一个回调函数，该回调函数将接收当前状态并返回新状态。
我们可以使用它来重构之前的代码：

<CodeBlock>{trimSnippet(updateReadOnce)}</CodeBlock>

这样就实现了相同的效果，而且使语法更好一些。

[ref.watch]: ../concepts/reading#using-refwatch-to-observe-a-provider
[ref.read]: ../concepts/reading#using-refread-to-obtain-the-state-of-a-provider-once
[statenotifierprovider]: ./state_notifier_provider
[futureprovider]: ./future_provider
[statenotifier]: https://pub.dev/documentation/state_notifier/latest/state_notifier/StateNotifier-class.html
[provider]: ./provider
[asyncvalue]: https://pub.dev/documentation/riverpod/latest/riverpod/AsyncValue-class.html
[future]: https://api.dart.dev/dart-async/Future-class.html
[family]: ../concepts/modifiers/family
